#include <algorithm>
#include "EpilExpressionInterpreter.h"
#include "EpilInterpreter.h"
#include "EpilDecimalExpressionCode.h"
#include "EpilOctalExpressionCode.h"
#include "EpilHexadecimalExpressionCode.h"
#include "EpilStringExpressionCode.h"
#include "EpilBooleanExpressionCode.h"
#include "EpilNilExpressionCode.h"
#include "EpilUnaryExpressionCode.h"
#include "EpilBinaryExpressionCode.h"
#include "EpilDoubleExpressionCode.h"
#include "EpilScientificExpressionCode.h"
#include "EpilIdentifierExpressionCode.h"
#include "EpilIndexExpressionCode.h"
#include "EpilInterpreterException.h"
#include "EpilFieldExpressionCode.h"
#include "EpilTableConstructorInterpreter.h"
#include "EpilFunctionBodyExpressionInterpreter.h"
#include "EpilFunctionCallExpressionCode.h"
#include "EpilMethodCallExpressionCode.h"
#include "EpilPointerHelper.h"

using namespace std;


EpilExpressionInterpreter::EpilExpressionInterpreter( EpilInterpreter * interpreter )
: EpilInterpreterBase( interpreter )
, m_object( NULL )
{
}


void EpilExpressionInterpreter::Run( const EpilExpressionCodeBase * code )
{
	switch( code->GetType() )
	{
	case ECT_NilExpression:
		{
			m_object = this->GetInterpreter()->CreateNilObject();
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_BooleanExpression:
		{
			const EpilBooleanExpressionCode * boolean = static_cast<const EpilBooleanExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateBooleanObject( boolean->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_DecimalExpression:
		{
			const EpilDecimalExpressionCode * decimal = static_cast<const EpilDecimalExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateNumberObject( decimal->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_OctalExpression:
		{
			const EpilOctalExpressionCode * octal = static_cast<const EpilOctalExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateNumberObject( octal->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_HexadecimalExpression:
		{
			const EpilHexadecimalExpressionCode * hex = static_cast<const EpilHexadecimalExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateNumberObject( hex->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_DoubleExpression:
		{
			const EpilDoubleExpressionCode * doubleCode = static_cast<const EpilDoubleExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateNumberObject( doubleCode->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_ScientificExpression:
		{
			const EpilScientificExpressionCode * scientific = static_cast<const EpilScientificExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateNumberObject( scientific->GetValue() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_StringExpression:
		{
			const EpilStringExpressionCode * str = static_cast<const EpilStringExpressionCode *>( code );
			m_object = this->GetInterpreter()->CreateStringObject( str->GetString() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_IdentifierExpression:
		{
			const EpilIdentifierExpressionCode * id = static_cast<const EpilIdentifierExpressionCode *>( code );
			m_object = this->GetInterpreter()->GetVariable( id->GetName() );
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_VariousParameterExpression:
		{

		}
		break;
	case ECT_UnaryExpression:
		{
			const EpilUnaryExpressionCode * unaryCode = static_cast<const EpilUnaryExpressionCode *>( code );
			
			EpilExpressionInterpreter ei( this->GetInterpreter() );
			ei.Run( unaryCode->GetExpression() );
			EpilObject * obj = ei.GetObject();

			try
			{
				m_object = obj->OperateUnary( unaryCode->GetOperator() );
			}
			catch( const EpilInterpreterException & e )
			{
				obj->ReleaseCopy();
				throw EpilInterpreterException( e.GetMessage(), code->GetLocation() );
			}
			m_multipleValues.push_back( m_object );
			obj->ReleaseCopy();
		}
		break;
	case ECT_BinaryExpression:
		{
			const EpilBinaryExpressionCode * binaryCode = static_cast<const EpilBinaryExpressionCode *>( code );
			
			EpilExpressionInterpreter leftInterpreter( this->GetInterpreter() );
			leftInterpreter.Run( binaryCode->GetLeftExpression() );
			EpilObject * leftObj = leftInterpreter.GetObject();

			EpilExpressionInterpreter rightInterpreter( this->GetInterpreter() );
			rightInterpreter.Run( binaryCode->GetRightExpression() );
			EpilObject * rightObj = rightInterpreter.GetObject();

			try
			{
				m_object = leftObj->OperateBinary( binaryCode->GetOperator(), rightObj );
				m_multipleValues.push_back( m_object );
			}
			catch( const EpilInterpreterException & e )
			{
				leftObj->ReleaseCopy();
				rightObj->ReleaseCopy();
				throw EpilInterpreterException( e.GetMessage(), code->GetLocation() );
			}

			leftObj->ReleaseCopy();
			rightObj->ReleaseCopy();
		}
		break;
	case ECT_IndexExpression:
		{
			const EpilIndexExpressionCode * indexCode = static_cast<const EpilIndexExpressionCode *>( code );
			const EpilExpressionCodeBase * tableExpr = indexCode->GetTableExpression();
			const EpilExpressionCodeBase * indexExpr = indexCode->GetIndexExpression();

			EpilExpressionInterpreter tableInterpreter( this->GetInterpreter() );
			tableInterpreter.Run( tableExpr );
			EpilObject * tableObj = tableInterpreter.GetObject();

			if( tableObj->GetType() != EOT_Table )
			{
				throw EpilInterpreterException( t("Attempt to index a non-table value \'") + tableExpr->ToEpilCode() + t("\'"), tableExpr->GetLocation() );
			}

			EpilExpressionInterpreter indexInterpreter( this->GetInterpreter() );
			indexInterpreter.Run( indexExpr );
			EpilObject * indexObj = indexInterpreter.GetObject();

			EpilTableObject * table = static_cast<EpilTableObject*>( tableObj );
			m_object = table->GetField( indexObj );
			m_multipleValues.push_back( m_object );

			tableObj->ReleaseCopy();
			indexObj->ReleaseCopy();
		}
		break;
	case ECT_FieldExpression:
		{
			const EpilFieldExpressionCode * fieldCode = static_cast<const EpilFieldExpressionCode *>( code );
			const EpilExpressionCodeBase * tableCode = fieldCode->GetTableExpression();

			EpilExpressionInterpreter ei( this->GetInterpreter() );
			ei.Run( tableCode );
			EpilObject * obj = ei.GetObject();

			if( obj->GetType() != EOT_Table )
			{
				obj->ReleaseCopy();
				throw EpilInterpreterException( t("Attempt to index a non-table value \'") + tableCode->ToEpilCode() + t("\'"), tableCode->GetLocation() );
			}
			EpilTableObject * table = static_cast<EpilTableObject*>( obj );
			m_object = table->GetField( fieldCode->GetFieldName() );
			m_multipleValues.push_back( m_object );

			table->ReleaseCopy();
		}
		break;
	case ECT_FunctionCallExpression:
		{
			const EpilFunctionCallExpressionCode * fce = static_cast<const EpilFunctionCallExpressionCode *>( code );
			const EpilExpressionCodeBase * functionExpr = fce->GetTableExpression();

			EpilExpressionInterpreter ei( this->GetInterpreter() );
			ei.Run( functionExpr );
			EpilObject * obj = ei.GetObject();

			if( obj->GetType() != EOT_Function )
			{
				obj->ReleaseCopy();
				throw EpilInterpreterException( t("Attempt to call a non-function value: \'") + functionExpr->ToEpilCode() + t("\'."), functionExpr->GetLocation() );
			}

			EpilFunctionObject * func = static_cast<EpilFunctionObject*>( obj );
			
			const EpilExpressionCodeList & argumentsCode = fce->GetArguments();
			EpilObjectVector arguments;
			for( EpilExpressionCodeList::const_iterator it = argumentsCode.begin(); it != argumentsCode.end(); ++it )
			{
				EpilExpressionInterpreter ei( this->GetInterpreter() );
				try
				{
					ei.Run( *it );
				}
				catch( ... )
				{
					func->ReleaseCopy();
					for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
					throw;
				}
				arguments.push_back( ei.GetObject() );
			}

			try
			{
				m_multipleValues = func->Invoke( arguments );
			}
			catch( ... )
			{
				func->ReleaseCopy();
				for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
				throw;
			}
			if( m_multipleValues.empty() == false )
			{
				m_object = m_multipleValues[0];
			}

			func->ReleaseCopy();
			for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
		}
		break;
	case ECT_MethodCallExpression:
		{
			const EpilMethodCallExpressionCode * mce = static_cast<const EpilMethodCallExpressionCode *>( code );
			const EpilExpressionCodeBase * tableExpr = mce->GetTableExpression();

			EpilExpressionInterpreter ei( this->GetInterpreter() );
			ei.Run( tableExpr );
			EpilObject * tableObj = ei.GetObject();

			if( tableObj->GetType() != EOT_Table )
			{
				tableObj->ReleaseCopy();
				throw EpilInterpreterException( t("Attempt to index a non-table value: \'") + tableExpr->ToEpilCode() + t("\'."), tableExpr->GetLocation() );
			}
			EpilTableObject * table = static_cast<EpilTableObject*>( tableObj );
			
			EpilObject * methodObj = table->GetField( mce->GetMethodName() );
			if( methodObj->GetType() != EOT_Function )
			{
				methodObj->ReleaseCopy();
				throw EpilInterpreterException( t("Attempt to call a non-function value: \'") + tableExpr->ToEpilCode() + t("\'.") + mce->GetMethodName(), tableExpr->GetLocation() );
			}
			EpilFunctionObject * method = static_cast<EpilFunctionObject*>( methodObj );
			
			const EpilExpressionCodeList & argumentsCode = mce->GetArguments();
			EpilObjectVector arguments;
			arguments.push_back( table->CreateCopy() );
			for( EpilExpressionCodeList::const_iterator it = argumentsCode.begin(); it != argumentsCode.end(); ++it )
			{
				EpilExpressionInterpreter ei( this->GetInterpreter() );
				try
				{
					ei.Run( *it );
				}
				catch( ... )
				{
					method->ReleaseCopy();
					for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
					throw;
				}
				arguments.push_back( ei.GetObject() );
			}

			try
			{
				m_multipleValues = method->Invoke( arguments );
			}
			catch( ... )
			{
				method->ReleaseCopy();
				for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
				throw;
			}
			if( m_multipleValues.empty() == false )
			{
				m_object = m_multipleValues[0];
			}

			method->ReleaseCopy();
			for_each( arguments.begin(), arguments.end(), mem_fun( &EpilObject::ReleaseCopy ) );
		}
		break;
	case ECT_FunctionBodyExpression:
		{
			const EpilFunctionBodyExpressionCode * bodyCode = static_cast<const EpilFunctionBodyExpressionCode *>( code );
			EpilFunctionBodyExpressionInterpreter fbei( this->GetInterpreter() );
			fbei.Run( bodyCode );
			m_object = fbei.GetFunction();
			m_multipleValues.push_back( m_object );
		}
		break;
	case ECT_TableConstructorExpression:
		{
			const EpilTableConstructorExpressionCode * constructorCode = static_cast<const EpilTableConstructorExpressionCode *>( code );
			EpilTableConstructorInterpreter tci( this->GetInterpreter() );
			tci.Run( constructorCode );
			m_object = tci.GetTable();
			m_multipleValues.push_back( m_object );
		}
		break;
	}
}